package camid.distribution.broker;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Future;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import camid.distribution.activeobject.ObjectScheduler;
import camid.distribution.events.FutureNotificator;
import camid.distribution.events.InvokerEvent;
import camid.distribution.interceptor.InvocationInterceptor;
import camid.distribution.lifecycle.ObjectManager;
import camid.distribution.protocol.InvocationDescriptor;
import camid.distribution.protocol.MethodRequestMessage;
import camid.distribution.protocol.MethodResponseMessage;
import camid.distribution.protocol.ResponseStatus;

public class Invoker {

	private ObjectManager objectManager;
	private List<InvocationInterceptor> interceptors;
	private FutureNotificator notificator;
	
	private static final Logger logger = LogManager.getLogger(Invoker.class.getName());
	
	public Invoker(ObjectManager objectManager) 
	{
		this.objectManager = objectManager;
		this.interceptors = Collections.synchronizedList(new ArrayList<InvocationInterceptor> ());
	}
	
	public Invoker(ObjectManager objectManager, FutureNotificator notificator) 
	{
		this.notificator = notificator;
		this.objectManager = objectManager;
		this.interceptors = Collections.synchronizedList(new ArrayList<InvocationInterceptor> ());
	}

	public MethodResponseMessage invoke(MethodRequestMessage req) {
		
		InvocationDescriptor descriptor = req.getDescriptor();
		
		MethodResponseMessage responseMsg = new MethodResponseMessage();
		
		InvokerEvent ie = new InvokerEvent();
		try 
		{
			synchronized(interceptors)
			{
				for(InvocationInterceptor interceptor : interceptors)
				{
					interceptor.beforeInvocation(req);
				}
			}
			
			ie.setObjectId(descriptor.getObjectId());
			ie.setEntityClass(descriptor.getEntityClass());
			ie.setMethodName(descriptor.getMethod());
			
			logger.info("Invoking " + descriptor.getEntityClass() 
					+ "/" + descriptor.getObjectId() + "/" + descriptor.getMethod());
			
			ie.startTimer();
			
			Object response = evaluateInvocation(descriptor);
			
			if( response instanceof Exception )
			{
				responseMsg.setResponse((Serializable)response);
				responseMsg.setStatus(ResponseStatus.SERVICE_EXCEPTION);
			}
			else
			{
				responseMsg.setResponse((Serializable)response);
				responseMsg.setStatus(ResponseStatus.SUCCESS);
			}
			
		}
		catch(Exception e)
		{
			responseMsg.setResponse(e);
			responseMsg.setStatus(ResponseStatus.SERVICE_EXCEPTION);
		}
		
		ie.stopTimer();
		if(notificator != null)
		{
			notificator.notifyEvent(ie);
		}
		
		synchronized(interceptors)
		{
			for(InvocationInterceptor interceptor : interceptors)
			{
				try 
				{
					interceptor.afterInvocation(responseMsg);
				} 
				catch (Exception e) 
				{
					responseMsg.setResponse(e);
					responseMsg.setStatus(ResponseStatus.SERVICE_EXCEPTION);
				}
			}
		}
		
		return responseMsg;
	}
	
	private Object evaluateInvocation( InvocationDescriptor descriptor )
	{
		Object response = null;
		try {
		
			ObjectScheduler scheduler = null;
			String objectId = descriptor.getObjectId();
			if( !objectManager.hasObject(objectId) )
			{
				String entityClassName = descriptor.getEntityClass();
				Class<?> entityClass = Class.forName(entityClassName);
				scheduler = objectManager.newObject(objectId, entityClass);
			}
			else
			{
				scheduler = objectManager.getObject(objectId);
			}
			
			if( scheduler != null )
			{
				String method = descriptor.getMethod();
				Object[] params = descriptor.getParams();
				Future<?> future = scheduler.submitTask(method, params);
				response = future.get();
			}
		}
		catch (Exception e) {
			e.printStackTrace();
			response = e;
		} 
		
		return response;
	}

	public void addInterceptor(InvocationInterceptor invocationInterceptor) {
		this.interceptors.add(invocationInterceptor);
		
	}
	

}
